﻿using Games.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Games.Box
{
    class Game
    {
        private byte[,] status = new byte[1, 1];
        private short[,] objs = new short[1, 1];
        private Stack<(Direction, bool)> steps = new Stack<(Direction, bool)>();
        public int MinStep { get; set; }

        public int CurrentX { get; private set; }
        public int CurrentY { get; private set; }
        public int Width { get; private set; }
        public int Height { get; private set; }

        public int BoxCount { get; private set; }
        public int CountOnPlace { get; private set; }

        public void Init(string map)
        {
            var ms = map.Split(';');
            this.Width = byte.Parse(ms[0]);
            this.Height = byte.Parse(ms[1]);
            this.MinStep = int.Parse(ms[3]);
            this.status = new byte[this.Width, this.Height];
            this.objs = new short[this.Width, this.Height];
            this.BoxCount = 0;
            this.CountOnPlace = 0;
            sbyte index = 1;
            this.steps.Clear();
            for(byte j = 0; j < this.Height; j++)
            {
                for(byte i = 0; i < this.Width; i++)
                {
                    var status = ms[2][j * this.Width + i] - 48;
                    this.status[i, j] = (byte)status;
                    if((status & CellStatus.Man) > 0)
                    {
                        this.CurrentX = i;
                        this.CurrentY = j;
                        this.objs[i, j] = 0;
                    }
                    else if((status & CellStatus.Box) > 0)
                    {
                        this.BoxCount++;
                        if((status & CellStatus.Target) > 0)
                        {
                            this.CountOnPlace++;
                        }
                        this.objs[i, j] = index;
                        index++;
                    }
                    else
                    {
                        this.objs[i, j] = -1;
                    }
                }
            }
        }

        private string Gets(int[,] s)
        {
            var str = "";
            for(int j = 0; j < this.Height; j++)
            {
                for(int i = 0; i < this.Width; i++)
                {
                    if(s[i, j] == -1)
                    {
                        str += "-";
                    }
                    else
                    {
                        str += s[i, j];
                    }
                }
                str += " ";
            }
            return str;
        }

        public async Task<bool> Move(Direction direction, Func<int, int, int, byte, Task> callback)
        {
            short x = 0, y = 0;
            if(direction == Direction.Right)
            {
                x = 1;
            }
            else if(direction == Direction.Left)
            {
                x = -1;
            }
            else if(direction == Direction.Up)
            {
                y = -1;
            }
            else if(direction == Direction.Down)
            {
                y = 1;
            }
            else
            {
                return false;
            }
            //移动一个位置
            var tcs = this.status[CurrentX + x, CurrentY + y];
            if(tcs == CellStatus.Wall)
            {
                return false;
            }
            //var statstr = Gets(this.status);
            //var objs = Gets(this.objs);
            var tasks = new List<Task>();
            //移动两个位置
            var hasPush = false;
            var sks = this.status[CurrentX + x * 2, CurrentY + y * 2];
            if(tcs == CellStatus.Box || tcs == CellStatus.BoxOnPlace)
            {
                if(sks == CellStatus.Wall || sks == CellStatus.Box || sks == CellStatus.BoxOnPlace)
                {
                    return false;
                }
                if(this.status[CurrentX + x * 2, CurrentY + y * 2] == CellStatus.Target)
                {
                    this.status[CurrentX + x * 2, CurrentY + y * 2] = CellStatus.BoxOnPlace;
                    this.CountOnPlace++;
                }
                else
                {
                    this.status[CurrentX + x * 2, CurrentY + y * 2] = CellStatus.Box;
                }
                this.objs[CurrentX + x * 2, CurrentY + y * 2] = this.objs[CurrentX + x, CurrentY + y];
                tasks.Add(callback(this.objs[CurrentX + x, CurrentY + y], CurrentX + x * 2, CurrentY + y * 2, this.status[CurrentX + x * 2, CurrentY + y * 2]));
                if(this.status[CurrentX + x, CurrentY + y] == CellStatus.BoxOnPlace)
                {
                    this.status[CurrentX + x, CurrentY + y] = CellStatus.ManOnPlace;
                    this.CountOnPlace--;
                }
                else
                {
                    this.status[CurrentX + x, CurrentY + y] = CellStatus.Man;
                }
                this.objs[CurrentX + x, CurrentY + y] = this.objs[CurrentX, CurrentY];
                tasks.Add(callback(this.objs[CurrentX, CurrentY], CurrentX + x, CurrentY + y, this.status[CurrentX + x, CurrentY + y]));
                if(this.status[CurrentX, CurrentY] == CellStatus.ManOnPlace)
                {
                    this.status[CurrentX, CurrentY] = CellStatus.Target;
                }
                else
                {
                    this.status[CurrentX, CurrentY] = CellStatus.Empty;
                }
                this.objs[CurrentX, CurrentY] = -1;
                hasPush = true;
            }
            else if(tcs == CellStatus.Empty || tcs == CellStatus.Target)
            {
                this.status[CurrentX, CurrentY] = this.status[CurrentX, CurrentY] == CellStatus.ManOnPlace ? CellStatus.Target : CellStatus.Empty;
                this.status[CurrentX + x, CurrentY + y] = tcs == CellStatus.Target ? CellStatus.ManOnPlace : CellStatus.Man;

                this.objs[CurrentX + x, CurrentY + y] = this.objs[CurrentX, CurrentY];
                tasks.Add(callback(this.objs[CurrentX, CurrentY], CurrentX + x, CurrentY + y, this.status[CurrentX + x, CurrentY + y]));
                this.objs[CurrentX, CurrentY] = -1;
            }
            CurrentX += x;
            CurrentY += y;
            if(tasks.Any())
            {
                await Task.WhenAll(tasks);
            }
            steps.Push((direction, hasPush));
            //statstr = Gets(this.status);
            //objs = Gets(this.objs);
            return true;
        }

        public async Task Back(Func<int, int, int, byte, Task> callback)
        {
            if(this.steps.Count == 0)
            {
                return;
            }
            //var statstr = Gets(this.status);
            //var objs = Gets(this.objs);
            var (direction, hasPush) = this.steps.Pop();
            int x = 0, y = 0;
            if(direction == Direction.Right)
            {
                x = -1;
            }
            else if(direction == Direction.Left)
            {
                x = 1;
            }
            else if(direction == Direction.Up)
            {
                y = 1;
            }
            else if(direction == Direction.Down)
            {
                y = -1;
            }
            var tasks = new List<Task>();
            var cs = this.status[CurrentX, CurrentY];
            var tcs = this.status[CurrentX + x, CurrentY + y];
            if(tcs == CellStatus.Target)
            {
                this.status[CurrentX + x, CurrentY + y] = CellStatus.ManOnPlace;
            }
            else if(tcs == CellStatus.Empty)
            {
                this.status[CurrentX + x, CurrentY + y] = CellStatus.Man;
            }
            this.objs[CurrentX + x, CurrentY + y] = 0;
            if(hasPush)
            {
                if(cs == CellStatus.ManOnPlace)
                {
                    this.status[CurrentX, CurrentY] = CellStatus.BoxOnPlace;
                    this.CountOnPlace++;
                }
                else if(cs == CellStatus.Man)
                {
                    this.status[CurrentX, CurrentY] = CellStatus.Box;
                }
                tasks.Add(callback(this.objs[CurrentX, CurrentY], CurrentX + x, CurrentY + y, this.status[CurrentX + x, CurrentY + y]));
                this.objs[CurrentX, CurrentY] = this.objs[CurrentX - x, CurrentY - y];
                var bcs = this.status[CurrentX - x, CurrentY - y];
                if(bcs == CellStatus.Box)
                {
                    this.status[CurrentX - x, CurrentY - y] = CellStatus.Empty;
                }
                else if(bcs == CellStatus.BoxOnPlace)
                {
                    this.status[CurrentX - x, CurrentY - y] = CellStatus.Target;
                    this.CountOnPlace--;
                }
                tasks.Add(callback(this.objs[CurrentX - x, CurrentY - y], CurrentX, CurrentY, this.status[CurrentX, CurrentY]));
                this.objs[CurrentX - x, CurrentY - y] = -1;
            }
            else
            {
                if(cs == CellStatus.ManOnPlace)
                {
                    this.status[CurrentX, CurrentY] = CellStatus.Target;
                }
                else if(cs == CellStatus.Man)
                {
                    this.status[CurrentX, CurrentY] = CellStatus.Empty;
                }
                tasks.Add(callback(this.objs[CurrentX, CurrentY], CurrentX + x, CurrentY + y, this.status[CurrentX + x, CurrentY + y]));
                this.objs[CurrentX, CurrentY] = -1;
            }
            CurrentX += x;
            CurrentY += y;
            if(tasks.Any())
            {
                await Task.WhenAll(tasks);
            }
            //statstr = Gets(this.status);
            //objs = Gets(this.objs);
        }

        public IEnumerable<(short, byte, byte, byte)> Deconstruct()
        {
            for(byte j = 0; j < this.Height; j++)
            {
                for(byte i = 0; i < this.Width; i++)
                {
                    var p = this.objs[i, j];
                    if(p >= 0)
                    {
                        var s = this.status[i, j];
                        yield return (p, i, j, s);
                    }
                }
            }
        }

        public IEnumerable<(byte, byte, byte)> DeconstructMap()
        {
            for(byte j = 0; j < this.Height; j++)
            {
                for(byte i = 0; i < this.Width; i++)
                {
                    var s = this.status[i, j];
                    if((s & CellStatus.Target) > 0 || (s & CellStatus.Wall) > 0)
                    {
                        yield return (i, j, s);
                    }
                }
            }
        }

        public bool IsFinished
        {
            get
            {
                return this.BoxCount == this.CountOnPlace;
            }
        }
    }
}
